#include "http_conn.h"

int http_conn::m_epollfd = -1;      // 所有socket上的事件都被注册到同一个epoll内核事件表中，所以将epoll文件描述符设置为静态的
int http_conn::m_user_count = 0;    // 统计用户数量

// 定义HTTP响应的一些状态信息
const char* ok_200_title = "OK";
const char* error_400_title = "Bad Request";
const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You do not have permission to get file from this server.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The requested file was not found on this server.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problem serving the requested file.\n";

// 网站的根目录
const char* doc_root = "/root/test_webserver/resources";

// 设置文件描述符为非阻塞
int setnonblocking(int fd) {
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

// 向epoll中添加需要监听的文件描述符
void addfd(int epollfd, int fd, bool one_shot) {
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;  // TODO：LT/ET
    if (one_shot) {
        // 防止同一个通信被不同线程处理
        event.events |= EPOLLONESHOT;
    }
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    // 设置文件描述符为非阻塞
    setnonblocking(fd);
}

// 从epoll中移除监听的文件描述符
void removefd(int epollfd, int fd) {
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0);
    close(fd);
}

// 修改epoll中监听的文件描述符，重置socket上的EPOLLONESHOT事件，以确保下一次可读时，EPOLLIN事件能够被触发
void modfd(int epollfd, int fd, int ev) {
    epoll_event event;
    event.data.fd = fd;
    // EPOLLONESHOT事件: 一个socket连接在任意时刻只被一个线程处理
    // 这里的EPOLLET与addfd中的EPOLLET不同: 这里的EPOLLET是边缘触发模式，只有状态发生变化时才触发事件
    event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

// 初始化连接
void http_conn::init(){
    m_check_state = CHECK_STATE_REQUESTLINE;    // 初始状态为解析请求行
    m_linger = false;       // 默认不保持连接
    m_method = GET;         // 默认请求方法为GET
    m_url = 0;              // 请求的URL
    m_version = 0;          // HTTP版本号
    m_content_length = 0;   // HTTP请求的消息体的长度
    m_host = 0;             // 主机名
    m_start_line = 0;       // 当前正在解析的行的起始位置
    m_checked_idx = 0;      // 当前正在解析的字符在读缓冲区中的位置
    m_read_idx = 0;         // 标识读缓冲区中已经读入的客户数据的最后一个字节的下一个位置
    m_write_idx = 0;        // 写缓冲区中待发送的字节数
    bzero(m_read_buf, READ_BUFFER_SIZE);
    bzero(m_write_buf, READ_BUFFER_SIZE);
    bzero(m_real_file, FILENAME_LEN);
}

// 初始化连接，外部调用初始化套接字地址
void http_conn::init(int sockfd, const sockaddr_in& addr){
    m_sockfd = sockfd;
    m_address = addr;
    // 端口复用
    int reuse = 1;
    setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    addfd(m_epollfd, m_sockfd, true);
    // 用户数加一
    m_user_count++;
    // 初始化, 为了下一次请求做准备
    init();
}

// 关闭连接
void http_conn::close_conn(){
    if(m_sockfd != -1){
        removefd(m_epollfd, m_sockfd);  
        m_sockfd = -1;  // 重置
        m_user_count--; // 关闭一个连接，将客户总量减一
    }
}

// 循环得去客户端数据，直到无数据可读或者对方关闭连接
bool http_conn::read(){
    // 读缓冲区已满
    if(m_read_idx >= READ_BUFFER_SIZE){
        return false;
    }
    int bytes_read = 0;
    while(true){
        // 从m_read_buf + m_read_idx位置开始读，大小是READ_BUFFER_SIZE - m_read_idx
        bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);
        if(bytes_read == -1){
            if(errno == EAGAIN || errno == EWOULDBLOCK){
                // 没有数据可读
                break;
            }
            return false;
        } else if(bytes_read == 0){ // 对方关闭连接
            return false;
        }
        m_read_idx += bytes_read;
        // printf("读到数据:\n%s", m_read_buf);
    }
    
    return true;
}

// 主状态机，用于从buffer中取出所有完整的行
http_conn::HTTP_CODE http_conn::process_read(){
    LINE_STATUS line_status = LINE_OK;  // 记录当前行的读取状态
    HTTP_CODE ret = NO_REQUEST;         // 记录HTTP请求的处理结果
    char* text = 0;                     // 指向读缓冲区中当前正在解析的字节
    while(m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK || (line_status = parse_line()) == LINE_OK){
        // 如果解析到了一个完整的行或者请求体，就开始分析这一行
        text = get_line();
        m_start_line = m_checked_idx;
        printf("got 1 http line: %s\n", text);
        // 根据主状态机的状态，决定处理逻辑
        switch(m_check_state){
            case CHECK_STATE_REQUESTLINE:
                ret = parse_request_line(text);
                if(ret == BAD_REQUEST){
                    return BAD_REQUEST;
                }
                break;
            case CHECK_STATE_HEADER:
                ret = parse_headers(text);
                if(ret == BAD_REQUEST){
                    return BAD_REQUEST;
                } else if(ret == GET_REQUEST){
                    return do_request();
                }
                break;
            case CHECK_STATE_CONTENT:
                ret = parse_content(text);
                if(ret == GET_REQUEST){
                    return do_request();
                }
                line_status = LINE_OPEN;
                break;
            default:
                return INTERNAL_ERROR;
        }
    }
    return NO_REQUEST;
}

// 从状态机，用于解析出一行内容，判断依据是\r\n
http_conn::LINE_STATUS http_conn::parse_line(){
    char temp;
    // m_checked_idx指向当前正在分析的字节，m_read_idx指向缓冲区末尾的下一个字节
    for(; m_checked_idx < m_read_idx; ++m_checked_idx){
        temp = m_read_buf[m_checked_idx];
        if(temp == '\r'){
            // 如果是最后一个字符，说明这一行还没有读完
            if((m_checked_idx + 1) == m_read_idx){
                return LINE_OPEN;
            } 
            // 如果下一个字符是\n，说明这一行读完了
            else if (m_read_buf[m_checked_idx + 1] == '\n'){
                m_read_buf[m_checked_idx++] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
        else if(temp == '\n'){
            // 如果前一个字符是\r，说明这一行读完了
            if((m_checked_idx > 1) && m_read_buf[m_checked_idx - 1] == '\r'){
                m_read_buf[m_checked_idx - 1] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    return LINE_OPEN;
}

// 解析请求行：获取请求方法、目标URL、HTTP版本号: GET /index.html HTTP/1.1
http_conn::HTTP_CODE http_conn::parse_request_line(char* text){
    // 因为HTTP请求行中的三个部分用空格分隔，所以可以通过strpbrk函数找到这两个空格
    m_url = strpbrk(text, " \t");   // 在text中找到第一个空格或制表符的位置
    if(!m_url){
        return BAD_REQUEST;
    }
    *m_url++ = '\0';        // 将第一个空格替换为\0
    char* method = text;
    
    if(strcasecmp(method, "GET") == 0){
        m_method = GET;
    } else {
        return BAD_REQUEST;
    }
    
    // m_url:/index.html HTTP/1.1
    // strpbrk函数返回的是第一个空格或制表符的位置，所以m_url指向URL的起始位置
    m_version = strpbrk(m_url, " \t");  // 跳过空格或制表符
    if(!m_version){
        return BAD_REQUEST;
    }
    *m_version++ = '\0';    // 将第二个空格替换为\0
    if(strcasecmp(m_version, "HTTP/1.1") != 0){
        return BAD_REQUEST;
    }
    
    // 也可能是"http://"开头，需要跳过，http://192.168.110.129:10000/index.html
    if(strncasecmp(m_url, "http://", 7) == 0){
        m_url += 7;
        m_url = strchr(m_url, '/'); // 找到第一个/的位置
    }

    // 检查URL是否合法
    if(!m_url || m_url[0] != '/'){
        return BAD_REQUEST;
    }
    // 请求行处理完毕，状态转移到头部字段的分析
    m_check_state = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

// 解析HTTP请求的头部字段：Host: 47.100.232.132:10000
http_conn::HTTP_CODE http_conn::parse_headers(char* text){
    // 遇到空行，表示头部字段解析完毫，状态转移到请求体的分析
    if(text[0] == '\0'){
        // 如果HTTP请求有消息体，则还需要读取m_content_length字节的消息体，状态转移到CHECK_STATE_CONTENT
        if(m_content_length != 0){
            m_check_state = CHECK_STATE_CONTENT;
            return NO_REQUEST;
        }
        // 否则说明得到了一个完整的HTTP请求
        return GET_REQUEST; 
    } else if(strncasecmp(text, "Host:", 5) == 0){
        // 处理头部字段Host
        text += 5;
        text += strspn(text, " \t");
        m_host = text;
    } else if(strncasecmp(text, "Connection:", 11) == 0){
        // 处理Connection字段
        text += 11;
        text += strspn(text, " \t");
        if(strcasecmp(text, "keep-alive") == 0){
            m_linger = true;
        }
    } else if(strncasecmp(text, "Content-Length:", 15) == 0){
        // 处理Content-Length字段
        text += 15;
        text += strspn(text, " \t");
        m_content_length = atol(text);
    } else {
        printf("oop! unknow header %s\n", text);

    }
    return NO_REQUEST;
}

// 解析请求体，暂时不处理
http_conn::HTTP_CODE http_conn::parse_content(char* text){
    // 如果读到的数据长度小于消息体长度，说明消息体还没有读完
    if(m_read_idx >= (m_content_length + m_checked_idx)){
        text[m_content_length] = '\0';
        return GET_REQUEST;
    }
    return NO_REQUEST;
}

// 处理请求，获取文件成功: 以只读方式打开文件，将文件映射到内存区
http_conn::HTTP_CODE http_conn::do_request(){
    strcpy(m_real_file, doc_root);
    int len = strlen(doc_root);
    // 将m_url中的URL后面的内容复制到m_real_file中
    strncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1);
    // 获取m_real_file的文件属性，如果文件不存在或者没有访问权限，则返回NO_RESOURCE
    if(stat(m_real_file, &m_file_stat) < 0){
        return NO_RESOURCE;
    }

    // 判断文件的权限，如果不可读，则返回FORBIDDEN_REQUEST
    if(!(m_file_stat.st_mode & S_IROTH)){
        return FORBIDDEN_REQUEST;
    }

    // 判断是否是目录，如果是目录，则返回BAD_REQUEST
    if(S_ISDIR(m_file_stat.st_mode)){
        return BAD_REQUEST;
    }

    // 以只读方式打开文件
    int fd = open(m_real_file, O_RDONLY);
    // 将文件映射到内存区
    m_file_address = (char*)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);
    return FILE_REQUEST;
}

// 非阻塞写
bool http_conn::write(){
    int temp = 0;
    int bytes_have_send = 0;    // 已经发送的字节数
    int bytes_to_send = m_write_idx;      // 将要发送的字节数 
    if (bytes_to_send == 0){
        // 没有数据需要发送
        modfd(m_epollfd, m_sockfd, EPOLLIN);
        init();
        return true;
    }
    while(true){
        // 分散写: 1. 保证数据完整性 2. 提高效率
        temp = writev(m_sockfd, m_iv, m_iv_count);
        if(temp <= -1) {
            // 如果TCP写缓冲没有空间，则等待下一轮EPOLLOUT事件, 虽然在此期间，服务器无法立刻接收到同一客户的下一个请求，但这可以保证连接的完整性
            if(errno == EAGAIN){
                // 没有数据可写
                modfd(m_epollfd, m_sockfd, EPOLLOUT);
                return true;
            }
            unmap();
            return false;
        }
        bytes_to_send -= temp;  // 更新待发送的字节数
        bytes_have_send += temp;// 更新已发送的字节数
        if(bytes_to_send <= bytes_have_send){
            // 发送HTTP响应成功，根据HTTP请求中的Connection字段决定是否立即关闭连接
            unmap();
            if(m_linger){
                init();
                modfd(m_epollfd, m_sockfd, EPOLLIN);
                return true;
            } else {
                modfd(m_epollfd, m_sockfd, EPOLLIN);
                return false;
            }
        }
    }
}

// 对内存映射区执行munmap操作：解除映射关系，释放映射区
void http_conn::unmap(){
    if(m_file_address){
        munmap(m_file_address, m_file_stat.st_size);
    }
}

// 根据HTTP请求的处理结果，决定返回给客户端的内容
bool http_conn::process_write(HTTP_CODE ret){
    switch(ret){
        case INTERNAL_ERROR:
            add_status_line(500, error_500_title);
            add_headers(strlen(error_500_form));
            if(!add_content(error_500_form)){
                return false;
            }
            break;
        case BAD_REQUEST:
            add_status_line(400, error_400_title);
            add_headers(strlen(error_400_form));
            if(!add_content(error_400_form)){
                return false;
            }
            break;
        case NO_RESOURCE:
            add_status_line(404, error_404_title);
            add_headers(strlen(error_404_form));
            if(!add_content(error_404_form)){
                return false;
            }
            break;
        case FORBIDDEN_REQUEST:
            add_status_line(403, error_403_title);
            add_headers(strlen(error_403_form));
            if(!add_content(error_403_form)){
                return false;
            }
            break;
        case FILE_REQUEST:
            add_status_line(200, ok_200_title);
            add_headers(m_file_stat.st_size);
            m_iv[0].iov_base = m_write_buf;
            m_iv[0].iov_len = m_write_idx;
            m_iv[1].iov_base = m_file_address;
            m_iv[1].iov_len = m_file_stat.st_size;
            m_iv_count = 2;
            return true;
        default:
            return false;
    }
    m_iv[0].iov_base = m_write_buf; // 第一个iovec指针指向m_write_buf
    m_iv[0].iov_len = m_write_idx;  // 第一个iovec指针的长度为m_write_idx
    m_iv_count = 1;
    return true;
}

// 添加状态行，HTTP/1.1 200 OK
bool http_conn::add_status_line(int status, const char* title){
    return add_response("%s %d %s\r\n", "HTTP/1.1", status, title);
}

// 向m_write_buf中写入响应报文数据
bool http_conn::add_response(const char* format, ...){
    // 如果写缓冲区可用空间不足，返回false
    if(m_write_idx >= WRITE_BUFFER_SIZE){
        return false;
    }
    va_list arg_list;   // 定义一个va_list类型的变量，用于存储传入的可变参数
    va_start(arg_list, format); // 初始化arg_list变量，使其指向第一个可变参数
    // 将可变参数按照format格式化成字符串，然后将其拷贝到m_write_buf中
    int len = vsnprintf(m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list);
    // 如果写入的数据长度超过了m_write_buf的剩余空间，返回false
    if(len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx)){
        return false;
    }
    m_write_idx += len; // 更新m_write_buf中待发送的字节数
    va_end(arg_list);
    return true;
}

// 添加消息头，Content-Length字段
void http_conn::add_headers(int content_length){
    add_content_length(content_length);
    add_content_type();
    add_linger();
    add_blank_line();
}

// 添加Content-Length字段
bool http_conn::add_content_length(int content_length){
    return add_response("Content-Length: %d\r\n", content_length);
}
// 添加Content-Type字段
bool http_conn::add_content_type(){
    return add_response("Content-Type: text/html\r\n");
}
// 添加Connection字段
bool http_conn::add_linger(){
    return add_response("Connection: %s\r\n", (m_linger == true) ? "keep-alive" : "close");
}
// 添加空行
bool http_conn::add_blank_line(){
    return add_response("%s", "\r\n");
}
// 添加消息体
bool http_conn::add_content(const char* content){
    return add_response("%s", content);
}

// 解析HTTP请求，由线程池中的工作线程调用，这是处理HTTP请求的入口函数
void http_conn::process(){
    HTTP_CODE read_ret = process_read();
    // 没有请求，继续监听读事件
    if(read_ret == NO_REQUEST){
        modfd(m_epollfd, m_sockfd, EPOLLIN);
        return;
    }
    // 生成响应
    bool write_ret = process_write(read_ret);
    if(!write_ret){
        close_conn();
    }
    modfd(m_epollfd, m_sockfd, EPOLLOUT);
}